package lava.rt.persistence.sql;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.FutureTask;
import java.util.function.Supplier;

import lava.rt.persistence.Entity;
import lava.rt.wrapper.GetSetFieldWarpper;

public abstract class TableTemplate<M extends Entity> extends ViewTemplate<M> {

	protected final String pkName;

	protected final GetSetFieldWarpper pkField;

	protected final GetSetFieldWarpper[] insertFields, updateFields;

	protected final String sqlInsert, sqlInsertWithoutPk, sqlUpdate, sqlDelete, sqlLoad, sqlColumns;

	public TableTemplate(SqlDataContext dataContext, String tableName, String pkName, Supplier<M> entityBuilder) {
		super(dataContext, tableName, entityBuilder);
		this.pkName = pkName;

		pkField = entityFieldMap.get(pkName);

		List<GetSetFieldWarpper> linsertFields = new ArrayList<>();
		for (GetSetFieldWarpper f : entityFieldMap.values()) {
			String fname = f.self.getName();
			if (fname.equalsIgnoreCase(pkName)) {
				continue;
			}
			linsertFields.add(f);
		}
		insertFields = linsertFields.toArray(new GetSetFieldWarpper[linsertFields.size()]);

		// insertFieldOffsets=new Long[insertFields.length];

		List<GetSetFieldWarpper> lupdateFields = new ArrayList<>();

		for (GetSetFieldWarpper f : entityFieldMap.values()) {
			String fname = f.self.getName();
			if (fname.equalsIgnoreCase(pkName)) {
				continue;
			}
			lupdateFields.add(f);
		}
		updateFields = lupdateFields.toArray(new GetSetFieldWarpper[lupdateFields.size()]);

		// updateFieldOffsets=new Long[updateFields.length];

		String sqlPattern = "insert into {0} ({1}) values ({2})", cols = "", vals = "";

		for (GetSetFieldWarpper field : insertFields) {

			String fname = field.self.getName();

			cols += fname + ",";
			vals += "?,";

		}
		cols = cols.substring(0, cols.length() - 1);
		vals = vals.substring(0, vals.length() - 1);
		sqlColumns = pkName + "," + cols;
		sqlInsert = MessageFormat.format(sqlPattern, tableName, sqlColumns, "?," + vals);
		sqlInsertWithoutPk = MessageFormat.format(sqlPattern, tableName, cols, vals);

		sqlPattern = "update {0} set {1} where {2}=? ";
		String key = "";

		for (GetSetFieldWarpper field : updateFields) {
			String fname = field.self.getName();

			key += fname + " =? ,";

		}
		key = key.substring(0, key.length() - 1);
		// key=LangCommon.repeat(" `{0}` =? ", ",", updateFields.length);
		sqlUpdate = MessageFormat.format(sqlPattern, this.tableName, key, this.pkName);

		sqlPattern = "delete from {0} where {1}=? ";

		sqlDelete = MessageFormat.format(sqlPattern, tableName, pkName);

		sqlLoad = MessageFormat.format("select {2} from {0} where {1}= ? ", this.tableName, this.pkName,
				this.sqlColumns);
	}

	public M loadByPk(Object pk) throws SqlDataContextExecption {
		Objects.requireNonNull(pk);
		String sql = sqlLoad;

		List<M> entrys = dataContext.<M>listEntities(entityBuilder, sql, pk);
		M ret = null;
		if (entrys.size() == 1) {
			ret = entrys.get(0);
		}
		return ret;
	}

	public <E extends M> FutureTask<Integer> insertBatchAsync(Collection<E> entrys) {

		FutureTask<Integer> ret = new FutureTask<>(() -> {

			return insertBatch(entrys);
			
		});

		return ret;

	}

	public <E extends M> int insertBatch(Collection<E> entrys) throws SqlDataContextExecption {
		Objects.requireNonNull(entrys);

		int insertsize = insertFields.length;

		Object[][] params = new Object[entrys.size()][insertsize + 1];
		try {
			int i = 0;

			for (M obj : entrys) {

				params[i][0] = obj.thisPk();
				for (int j = 0; j < insertsize; j++) {
					GetSetFieldWarpper field = insertFields[j];
					params[i][j + 1] = field.getter.invoke(obj);

				}
				i++;
			}
		} catch (Exception e) {
			// throw new ExecuteExecption(e);
		}

		int re = dataContext.executeBatch(sqlInsert, params);
		return re;

	}

	public <E extends M> FutureTask<Integer> insertAsync(final E entry) {

		FutureTask<Integer> ret = new FutureTask<>(() ->{

			return insert(entry);
			
		});

		return ret;

	}

	public <E extends M> int insert(E entry) throws SqlDataContextExecption {
		Objects.requireNonNull(entry);
		int insertsize = insertFields.length;
		final Object[] _param = new Object[insertsize + 1];
		try {
			_param[0] = entry.thisPk();
			for (int j = 0; j < insertsize; j++) {
				GetSetFieldWarpper field = insertFields[j];
				_param[j + 1] = field.getter.invoke(entry);

			}
		} catch (Exception e) {
			// throw new ExecuteExecption(e);
		}
		int re = 0;

		re = dataContext.executeUpdate(sqlInsert, _param);
		return re;

	}

	public <E extends M> FutureTask<Integer> updateAsync(E entry) {

		FutureTask<Integer> ret = new FutureTask<>(() -> {

			return update(entry);
			
		});

		return ret;

	}

	public <E extends M> int update(E entry) throws SqlDataContextExecption {
		Objects.requireNonNull(entry);
		Object pk = entry.thisPk();

		Objects.requireNonNull(pk);

		int updatesize = updateFields.length;
		Object[] param = new Object[updatesize + 1];

		try {
			for (int j = 0; j < updatesize; j++) {
				GetSetFieldWarpper field = updateFields[j];
				param[j] = field.getter.invoke(entry);
			}
		} catch (Exception ex) {
			// throw new ExecuteExecption(ex);
		}
		param[updatesize] = pk;

		int re = dataContext.executeUpdate(sqlUpdate, param);
		return re;

	}

	public <E extends M> FutureTask<Integer> updateBatchAsync(Collection<E> entrys) {
		FutureTask<Integer> ret = new FutureTask<>(() -> {
            return updateBatch(entrys);
		});

		return ret;
	}

	public <E extends M> int updateBatch(Collection<E> entrys) throws SqlDataContextExecption {

		Objects.requireNonNull(entrys);

		int updatesize = updateFields.length;
		final Object[][] params = new Object[entrys.size()][updatesize + 1];
		try {
			int i = 0;

			for (M obj : entrys) {

				for (int j = 0; j < updatesize; j++) {
					GetSetFieldWarpper field = updateFields[j];
					params[i][j] = field.getter.invoke(obj);

				}
				params[i][updatesize] = obj.thisPk();
				i++;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			// throw CommandExecuteExecption.forSql(e, cmd, param)(e);
		}

		int re = dataContext.executeBatch(sqlUpdate, params);
		return re;

	}

	public FutureTask<Integer> deleteAsync(String where, Object... param) {
		FutureTask<Integer> ret = new FutureTask<>(()->{
			return delete(where, param);
		});

		return ret;
	}

	public int delete(String where, Object... param) throws SqlDataContextExecption {
		Objects.requireNonNull(where);
		StringBuffer sql = new StringBuffer("delete from ");
		sql.append(tableName);

		sql.append(" where ").append(where);

		int ret = dataContext.executeUpdate(sql.toString(), param);
		return ret;

	}

	public FutureTask<Integer> updateAsync(String set, String where, Object... param) {
		FutureTask<Integer> ret = new FutureTask<>(()-> {
			
				return update(set, where, param);
			
		});

		return ret;
	}

	public int update(String set, String where, Object... param) throws SqlDataContextExecption {
		Objects.requireNonNull(set);
		Objects.requireNonNull(where);
		StringBuffer sql = new StringBuffer("update ");
		sql.append(tableName).append(" set ").append(set);

		sql.append(" where ").append(where);

		int re = dataContext.executeUpdate(sql.toString(), param);
		return re;
	}

	public <E extends M> FutureTask<Integer> deleteAsync(E entry) {
		FutureTask<Integer> ret = new FutureTask<>(() ->{
			return delete(entry);
		});

		return ret;

	}

	public <E extends M> int delete(E entry) throws SqlDataContextExecption {
		final Object pk = entry.thisPk();
		int ret = deleteByPk(pk);

		return ret;

	}

	public FutureTask<Integer> deleteByPkAsync(Object pk) {
		FutureTask<Integer> ret = new FutureTask<>(()-> {
			return deleteByPk(pk);
		});

		return ret;
	}

	public int deleteByPk(Object pk) throws SqlDataContextExecption {
		Objects.requireNonNull(pk);

		int re = dataContext.executeUpdate(sqlDelete, new Object[] { pk });
		return re;
	}

	public <P> FutureTask<Integer> deleteBatchByPkAsync(P[] pks) {
		FutureTask<Integer> ret = new FutureTask<>(() -> {
			return deleteBatchByPk(pks);
		});

		return ret;
	}

	public <P> int deleteBatchByPk(P[] pks) throws SqlDataContextExecption {

		int dlength = pks.length;
		Object[][] params = new Object[dlength][1];
		int i = 0;
		for (P pk : pks) {
			params[i][0] = pk;
			i++;
		}

		int re = dataContext.executeBatch(sqlDelete, params);
		return re;

	}

}
